/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.json.jackson;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.apexes.commons.lang.Checks;
import net.apexes.commons.net.JsonHttpClient;

/**
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public final class JacksonHttps {

    private JacksonHttps() {}

    public static JsonHttpClient.JsonEncoder createEncoder() {
        return createEncoder(null);
    }

    public static JsonHttpClient.JsonEncoder createEncoder(ObjectMapper objectMapper) {
        return new JacksonEncoder(objectMapper);
    }

    public static <R> JsonHttpClient.JsonDecoder<R> createDecoder(Class<R> returnClass) {
        return createDecoder(null, returnClass);
    }

    public static <R> JsonHttpClient.JsonDecoder<R> createDecoder(ObjectMapper objectMapper, Class<R> returnClass) {
        return new JacksonDecoder<>(objectMapper, returnClass);
    }

    public static <R> JsonHttpClient.JsonDecoder<R> createDecoder(JavaType javaType) {
        return createDecoder(null, javaType);
    }

    public static <R> JsonHttpClient.JsonDecoder<R> createDecoder(ObjectMapper objectMapper, JavaType javaType) {
        return new JacksonDecoder<>(objectMapper, javaType);
    }

    public static <R> JsonHttpClient.JsonDecoder<R> createDecoder(ObjectMapper objectMapper, TypeReference<R> typeRef) {
        return new JacksonTypeReferenceDecoder<>(objectMapper, typeRef);
    }

    public static JsonHttpClient.JsonHttpNoticer forNotice(String url) {
        return forNotice(url, createEncoder());
    }

    public static JsonHttpClient.JsonHttpNoticer forNotice(String url, JsonHttpClient.JsonEncoder encoder) {
        return JsonHttpClient.forNotice(url, encoder);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, Class<R> returnClass) {
        return forRequest(url, null, returnClass);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, ObjectMapper objectMapper, Class<R> returnClass) {
        JsonHttpClient.JsonEncoder encoder = createEncoder(objectMapper);
        JsonHttpClient.JsonDecoder<R> decoder = createDecoder(objectMapper, returnClass);
        return JsonHttpClient.forRequest(url, encoder, decoder);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, JavaType javaType) {
        return forRequest(url, null, javaType);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, ObjectMapper objectMapper, JavaType javaType) {
        JsonHttpClient.JsonEncoder encoder = createEncoder(objectMapper);
        JsonHttpClient.JsonDecoder<R> decoder = createDecoder(objectMapper, javaType);
        return JsonHttpClient.forRequest(url, encoder, decoder);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, TypeReference<R> typeRef) {
        return forRequest(url, null, typeRef);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, ObjectMapper objectMapper, TypeReference<R> typeRef) {
        JsonHttpClient.JsonEncoder encoder = createEncoder(objectMapper);
        JsonHttpClient.JsonDecoder<R> decoder = createDecoder(objectMapper, typeRef);
        return JsonHttpClient.forRequest(url, encoder, decoder);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, JsonHttpClient.JsonDecoder<R> decoder) {
        JsonHttpClient.JsonEncoder encoder = createEncoder();
        return JsonHttpClient.forRequest(url, encoder, decoder);
    }

    public static <R> JsonHttpClient<R> forRequest(String url, JsonHttpClient.JsonEncoder encoder, JsonHttpClient.JsonDecoder<R> decoder) {
        return JsonHttpClient.forRequest(url, encoder, decoder);
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private static class JacksonEncoder implements JsonHttpClient.JsonEncoder {

        private final ObjectMapper objectMapper;

        JacksonEncoder(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper != null ? objectMapper : Jacksons.defaultObjectMapper;
        }

        @Override
        public String toJson(Object object) throws Exception {
            if (object == null) {
                return null;
            }
            return objectMapper.writeValueAsString(object);
        }
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     * @param <R>
     */
    private static class JacksonDecoder<R> implements JsonHttpClient.JsonDecoder<R> {

        private final ObjectMapper objectMapper;
        private final JavaType javaType;

        JacksonDecoder(ObjectMapper objectMapper, Class<R> returnClass) {
            this(objectMapper, javaType(objectMapper, returnClass));
        }

        JacksonDecoder(ObjectMapper objectMapper, JavaType javaType) {
            Checks.verifyNotNull(javaType, "javaType");
            this.objectMapper = defaultIfNull(objectMapper);
            this.javaType = javaType;
        }

        @Override
        public R fromJson(String json) throws Exception {
            return objectMapper.readValue(json, javaType);
        }

        private static ObjectMapper defaultIfNull(ObjectMapper objectMapper) {
            return objectMapper != null ? objectMapper : Jacksons.defaultObjectMapper;
        }

        private static JavaType javaType(ObjectMapper objectMapper, Class<?> returnClass) {
            objectMapper = defaultIfNull(objectMapper);
            return objectMapper.getTypeFactory().constructType(returnClass);
        }

    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     * @param <R>
     */
    private static class JacksonTypeReferenceDecoder<R> implements JsonHttpClient.JsonDecoder<R> {

        private final ObjectMapper objectMapper;
        private final TypeReference<R> typeRef;

        JacksonTypeReferenceDecoder(ObjectMapper objectMapper, TypeReference<R> typeRef) {
            Checks.verifyNotNull(typeRef, "typeRef");
            this.objectMapper = objectMapper != null ? objectMapper : Jacksons.defaultObjectMapper;
            this.typeRef = typeRef;
        }

        @Override
        public R fromJson(String json) throws Exception {
            return objectMapper.readValue(json, typeRef);
        }

    }

}
